home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 2 / Apprentice-Release2.iso / Source Code / C / Applications / Newswatcher 2.0b22 / NW Source / Source / cache.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-12-04  |  22.6 KB  |  823 lines  |  [TEXT/MMCC]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     cache.c
  4.  
  5.     This module manages the article information cache.
  6.     
  7.     Copyright © 1994, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <string.h>
  12. #include <stdio.h>
  13. #include <stdlib.h>
  14. #include <Packages.h>
  15.  
  16. #include "glob.h"
  17. #include "cache.h"
  18. #include "dialog.h"
  19. #include "memutil.h"
  20. #include "text.h"
  21. #include "fileutil.h"
  22. #include "windutil.h"
  23. #include "resutil.h"
  24. #include "ic.h"
  25.  
  26.  
  27.  
  28. #define kCacheResourceType        'ACCH'
  29. #define kCacheGroupArrayID        128
  30. #define kCacheArticleArrayID    129
  31. #define kCacheStringsBlockID    130
  32.  
  33.  
  34.  
  35. typedef struct TGroupInfo {
  36.     long offset;                /* offset in strings block of group name */
  37.     long numCached;                /* number of cached articles in this group */
  38. } TGroupInfo;
  39.  
  40. typedef struct TArticleInfo {
  41.     long groupIndex;            /* index in group info array, or -1 if entry not used */
  42.     long number;                /* article number */
  43.     long subjectOffset;            /* offset in strings block of subject string */
  44.     long authorOffset;            /* offset in strings block of author string */
  45.     unsigned long creationDateTime;    /* date/time cache entry was created */
  46. } TArticleInfo;
  47.  
  48.  
  49. static Boolean gCacheDirty = false;            /* true if cache changed since read from prefs file */
  50. static TGroupInfo **gGroupInfo = nil;        /* handle to array of group info */
  51. static long gNumGroupInfo = 0;                /* number of elements in group info array */
  52. static TArticleInfo **gArticleInfo = nil;    /* handle to cached article info */
  53. static long gNumArticleInfo = 0;            /* number of elements in article info array */
  54. static Handle gStrings = nil;                /* handle to strings block */
  55. static long gStringsAllocated = 0;            /* number of bytes allocated in strings block */
  56. static long gStringsUsed = 0;                /* number of bytes used in strings block */
  57.     
  58.  
  59.  
  60. /*----------------------------------------------------------------------------
  61.     ValidCache 
  62.     
  63.     Validate the cache.
  64.             
  65.     Exit:    function result = true if no error.
  66. ----------------------------------------------------------------------------*/
  67.  
  68. static Boolean ValidCache (void)
  69. {
  70.     long i, j, numCached;
  71.     TArticleInfo *p;
  72.     TGroupInfo *q;
  73.     unsigned long nowDateTimePlus24Hours;
  74.  
  75.     GetDateTime(&nowDateTimePlus24Hours);
  76.     nowDateTimePlus24Hours += 24L*60L*60L;
  77.  
  78.     if (MyGetHandleSize(gGroupInfo) != gNumGroupInfo*sizeof(TGroupInfo)) goto exit;
  79.     for (i = 0, q = *gGroupInfo; i < gNumGroupInfo; i++, q++) {
  80.         if (q->offset < 0) goto exit;
  81.         if (q->offset >= gStringsUsed) goto exit;
  82.         if (strlen(*gStrings + q->offset) > 255) goto exit;
  83.         numCached = 0;
  84.         for (j = 0, p = *gArticleInfo; j < gNumArticleInfo; j++, p++)
  85.             if (p->groupIndex == i) numCached++;
  86.         if (numCached != q->numCached) goto exit;
  87.     }
  88.     
  89.     if (MyGetHandleSize(gArticleInfo) != gNumArticleInfo*sizeof(TArticleInfo)) goto exit;
  90.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  91.         if (p->groupIndex < 0) goto exit;
  92.         if (p->groupIndex >= gNumGroupInfo) goto exit;
  93.         if (p->number <= 0) goto exit;
  94.         if (p->subjectOffset < 0) goto exit;
  95.         if (p->subjectOffset >= gStringsUsed) goto exit;
  96.         if (strlen(*gStrings + p->subjectOffset) > 255) goto exit;
  97.         if (p->authorOffset < 0) goto exit;
  98.         if (p->authorOffset >= gStringsUsed) goto exit;
  99.         if (strlen(*gStrings + p->authorOffset) > 255) goto exit;
  100.         if (p->creationDateTime > nowDateTimePlus24Hours) goto exit;
  101.     }
  102.     
  103.     return true;
  104.     
  105. exit:
  106.  
  107.     ErrorMessageNumber(kStrDamagedCache);
  108.     return false;
  109. }
  110.     
  111.  
  112.  
  113. /*----------------------------------------------------------------------------
  114.     ReadCache 
  115.     
  116.     Read the article cache from the prefs file.
  117.             
  118.     Exit:    function result = error code.
  119.     
  120.     This function must be called during initialization, when the prefs
  121.     file is read.
  122. ----------------------------------------------------------------------------*/
  123.  
  124. OSErr ReadCache (void)
  125. {
  126.     OSErr err = noErr;
  127.  
  128.     err = MyGet1Resource(kCacheResourceType, kCacheGroupArrayID, &gGroupInfo);
  129.     if (err == resNotFound) {
  130.         err = MyNewHandle(0, &gStrings);
  131.         if (err != noErr) goto exit;
  132.         err = MyNewHandle(0, &gGroupInfo);
  133.         if (err != noErr) goto exit;
  134.         err = MyNewHandle(0, &gArticleInfo);
  135.         if (err != noErr) goto exit;
  136.     } else if (err == noErr) {
  137.         DetachResource((Handle)gGroupInfo);
  138.         gNumGroupInfo = MyGetHandleSize(gGroupInfo) / sizeof(TGroupInfo);
  139.         err = MyGet1Resource(kCacheResourceType, kCacheArticleArrayID, &gArticleInfo);
  140.         if (err != noErr) goto exit;
  141.         DetachResource((Handle)gArticleInfo);
  142.         gNumArticleInfo = MyGetHandleSize(gArticleInfo) / sizeof(TArticleInfo);
  143.         err = MyGet1Resource(kCacheResourceType, kCacheStringsBlockID, &gStrings);
  144.         if (err != noErr) goto exit;
  145.         DetachResource(gStrings);
  146.         gStringsAllocated = gStringsUsed = MyGetHandleSize(gStrings);
  147.         if (!ValidCache()) {
  148.             FlushCache();
  149.             return userCanceledErr;
  150.         }
  151.     } else {
  152.         goto exit;
  153.     }
  154.     return noErr;
  155.     
  156. exit:
  157.  
  158.     MyDisposeHandle(gGroupInfo);
  159.     MyDisposeHandle(gArticleInfo);
  160.     MyDisposeHandle(gStrings);
  161.     gGroupInfo = nil;
  162.     gArticleInfo = nil;
  163.     gStrings = nil;
  164.     gNumGroupInfo = gNumArticleInfo = gStringsAllocated = gStringsUsed = 0;
  165.     gCacheDirty = true;
  166.     return err;
  167. }
  168.  
  169.  
  170.  
  171. /*----------------------------------------------------------------------------
  172.     CompactCache 
  173.     
  174.     Compact the cache.
  175. ----------------------------------------------------------------------------*/
  176.  
  177. void CompactCache (void)
  178. {
  179.     TGroupInfo **groupInfo = nil, *q1, *q2;
  180.     TArticleInfo **articleInfo = nil, *p1, *p2;
  181.     Handle strings = nil;
  182.     long numGroupInfo, numArticleInfo, stringsUsed, size, i, j, k;
  183.     OSErr err = noErr;
  184.     short sLen, aLen;
  185.     unsigned long nowDateTimeMinus60Days;
  186.  
  187.     if (gGroupInfo == nil) return;
  188.     GetDateTime(&nowDateTimeMinus60Days);
  189.     nowDateTimeMinus60Days -= 60L*24L*60L*60L;
  190.  
  191.     MySetHandleSize(gStrings, gStringsUsed);
  192.     
  193.     size = MyGetHandleSize(gGroupInfo);
  194.     err = MyNewHandle(size, &groupInfo);
  195.     if (err != noErr) goto exit;
  196.     numGroupInfo = 0;
  197.     
  198.     size = MyGetHandleSize(gArticleInfo);
  199.     err = MyNewHandle(size, &articleInfo);
  200.     if (err != noErr) goto exit;
  201.     BlockMoveData(*gArticleInfo, *articleInfo, size);
  202.     numArticleInfo = 0;
  203.     
  204.     size = MyGetHandleSize(gStrings);
  205.     err = MyNewHandle(size, &strings);
  206.     if (err != noErr) goto exit;
  207.     stringsUsed = 0;
  208.     
  209.     for (i = 0, j = 0, q1 = *groupInfo, q2 = *gGroupInfo; j < gNumGroupInfo; j++, q2++) {
  210.         if (q2->numCached > 0) {
  211.             q1->offset = stringsUsed;
  212.             q1->numCached = 0;
  213.             strcpy(*strings + stringsUsed, *gStrings + q2->offset);
  214.             stringsUsed += strlen(*strings + stringsUsed) + 1;
  215.             numGroupInfo++;
  216.             q1++;
  217.             if (i != j) {
  218.                 for (k = 0, p1 = *articleInfo; k < gNumArticleInfo; k++, p1++)
  219.                     if (p1->groupIndex == j) p1->groupIndex = i;
  220.             }
  221.             i++;
  222.         }
  223.     }
  224.     MySetHandleSize(groupInfo, numGroupInfo * sizeof(TGroupInfo));
  225.     
  226.     for (j = 0, p1 = *articleInfo, p2 = *articleInfo; j < gNumArticleInfo; j++, p2++) {
  227.         if (p2->groupIndex >= 0 && p2->groupIndex < numGroupInfo &&
  228.             p2->creationDateTime >= nowDateTimeMinus60Days) 
  229.         {
  230.             sLen = strlen(*gStrings + p2->subjectOffset);
  231.             aLen = strlen(*gStrings + p2->authorOffset);
  232.             p1->groupIndex = p2->groupIndex;
  233.             p1->number = p2->number;
  234.             strcpy(*strings + stringsUsed, *gStrings + p2->subjectOffset);
  235.             p1->subjectOffset = stringsUsed;
  236.             stringsUsed += sLen + 1;
  237.             strcpy(*strings + stringsUsed, *gStrings + p2->authorOffset);
  238.             p1->authorOffset = stringsUsed;
  239.             stringsUsed += aLen + 1;
  240.             p1->creationDateTime = p2->creationDateTime;
  241.             (*groupInfo)[p1->groupIndex].numCached++;
  242.             numArticleInfo++;
  243.             p1++;
  244.         }
  245.     }
  246.     MySetHandleSize(articleInfo, numArticleInfo * sizeof(TArticleInfo));
  247.     
  248.     MySetHandleSize(strings, stringsUsed);
  249.     
  250.     MyDisposeHandle(gGroupInfo);
  251.     gGroupInfo = groupInfo;
  252.     gNumGroupInfo = numGroupInfo;
  253.     MyDisposeHandle(gArticleInfo);
  254.     gArticleInfo = articleInfo;
  255.     gNumArticleInfo = numArticleInfo;
  256.     MyDisposeHandle(gStrings);
  257.     gStrings = strings;
  258.     gStringsUsed = gStringsAllocated = stringsUsed;
  259.     
  260.     gCacheDirty = true;
  261.     
  262.     return;
  263.     
  264. exit:
  265.  
  266.     MyDisposeHandle(strings);
  267.     MyDisposeHandle(groupInfo);
  268.     MyDisposeHandle(articleInfo);
  269. }
  270.  
  271.  
  272.  
  273. /*----------------------------------------------------------------------------
  274.     FlushCache 
  275.     
  276.     Flush the cache.
  277. ----------------------------------------------------------------------------*/
  278.  
  279. void FlushCache (void)
  280. {
  281.     if (gGroupInfo == nil) return;
  282.     MySetHandleSize(gGroupInfo, 0);
  283.     gNumGroupInfo = 0;
  284.     MySetHandleSize(gArticleInfo, 0);
  285.     gNumArticleInfo = 0;
  286.     MySetHandleSize(gStrings, 0);
  287.     gStringsUsed = gStringsAllocated = 0;
  288.     gCacheDirty = true;
  289. }
  290.  
  291.  
  292.  
  293. /*----------------------------------------------------------------------------
  294.     WriteCache 
  295.     
  296.     Write the article cache to the prefs file.
  297.     
  298.     Entry:    newsServerAtStartup = news server name at startup.
  299.             
  300.     Exit:    function result = error code.
  301.     
  302.     This function must be called during termination, when the prefs file
  303.     is written.
  304. ----------------------------------------------------------------------------*/
  305.  
  306. OSErr WriteCache (Str255 newsServerAtStartup)
  307. {
  308.     OSErr err = noErr;
  309.     
  310.     MyICReadSharedPrefs(kICNNTPHost);
  311.  
  312.     if (!gCacheDirty) return noErr;
  313.     
  314.     if (gGroupInfo == nil) {
  315.         err = MyNewHandle(0, &gGroupInfo);
  316.         if (err != noErr) return err;
  317.         err = MyNewHandle(0, &gArticleInfo);
  318.         if (err != noErr) return err;
  319.         err = MyNewHandle(0, &gStrings);
  320.         if (err != noErr) return err;
  321.     } else if (!EqualString(gPrefs.newsServerName, newsServerAtStartup, false, true)) {
  322.         FlushCache();
  323.     } else {
  324.         CompactCache();
  325.     }
  326.     
  327.     /* Rewrite the three cache resources on the prefs file. */
  328.     
  329.     err = MyReplaceResource(gGroupInfo, kCacheResourceType, kCacheGroupArrayID, "\p");
  330.     if (err != noErr) return err;
  331.     err = MyReplaceResource(gArticleInfo, kCacheResourceType, kCacheArticleArrayID, "\p");
  332.     if (err != noErr) return err;
  333.     err = MyReplaceResource(gStrings, kCacheResourceType, kCacheStringsBlockID, "\p");
  334.     if (err != noErr) return err;
  335.     return noErr;
  336. }
  337.  
  338.  
  339.  
  340. /*----------------------------------------------------------------------------
  341.     AddCachedArticle 
  342.     
  343.     Add an article to the cache.
  344.     
  345.     Entry:    groupName = group name.
  346.             number = article number.
  347.             subject = subject.
  348.             author = author.
  349.             
  350.     Exit:    function result = error code.
  351. ----------------------------------------------------------------------------*/
  352.  
  353. OSErr AddCachedArticle (char *groupName, long number, char *subject, char *author)
  354. {
  355.     long i;
  356.     TArticleInfo *p;
  357.     TGroupInfo *q;
  358.     long index = -1, groupIndex, subjectOffset, authorOffset, offset;
  359.     OSErr err = noErr;
  360.     short len, sLen, aLen;
  361.     
  362.     if (gGroupInfo == nil) return noErr;
  363.  
  364.     /* Check to see if this article is already in the cache.
  365.        Also get index = index in article info array of a free entry, or
  366.        -1 if none. */
  367.  
  368.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  369.         if (p->groupIndex >= 0) {
  370.             if (number == p->number &&
  371.                 strcmp(groupName, *gStrings + (*gGroupInfo)[p->groupIndex].offset) == 0) 
  372.                     return noErr;
  373.         } else {
  374.             index = i;
  375.         }
  376.     }
  377.     
  378.     /* If necessary, add the group name to the strings block and to the group info array. */
  379.     
  380.     for (groupIndex = 0, q = *gGroupInfo; groupIndex < gNumGroupInfo; groupIndex++, q++) {
  381.         if (strcmp(groupName, *gStrings + q->offset) == 0) break;
  382.     }
  383.     if (groupIndex >= gNumGroupInfo) {
  384.         len = strlen(groupName);
  385.         if (gStringsUsed + len + 1 > gStringsAllocated) {
  386.             err = MySetHandleSize(gStrings, gStringsAllocated+1000);
  387.             if (err != noErr) return err;
  388.             gStringsAllocated += 1000;
  389.         }
  390.         strcpy(*gStrings + gStringsUsed, groupName);
  391.         offset = gStringsUsed;
  392.         gStringsUsed += len+1;
  393.         err = MySetHandleSize(gGroupInfo, (gNumGroupInfo+1) * sizeof(TGroupInfo));
  394.         if (err != noErr) return err;
  395.         groupIndex = gNumGroupInfo;
  396.         q = &(*gGroupInfo)[groupIndex];
  397.         q->offset = offset;
  398.         q->numCached = 0;
  399.         gNumGroupInfo++;
  400.     }
  401.     
  402.     /* Add the subject and author strings to the strings block. */
  403.     
  404.     sLen = strlen(subject);
  405.     aLen = strlen(author);
  406.     if (gStringsUsed + sLen + aLen + 2 > gStringsAllocated) {
  407.         err = MySetHandleSize(gStrings, gStringsAllocated+1000);
  408.         if (err != noErr) return err;
  409.         gStringsAllocated += 1000;
  410.     }
  411.     strcpy(*gStrings + gStringsUsed, subject);
  412.     subjectOffset = gStringsUsed;
  413.     gStringsUsed += sLen+1;
  414.     len = strlen(author);
  415.     strcpy(*gStrings + gStringsUsed, author);
  416.     authorOffset = gStringsUsed;
  417.     gStringsUsed += aLen+1;
  418.     
  419.     /* Add the new cache entry to the article info array. */
  420.     
  421.     if (index == -1) {
  422.         err = MySetHandleSize(gArticleInfo, (gNumArticleInfo+1) * sizeof(TArticleInfo));
  423.         if (err != noErr) return err;
  424.         index = gNumArticleInfo;
  425.         gNumArticleInfo++;
  426.     }
  427.     p = &(*gArticleInfo)[index];
  428.     p->groupIndex = groupIndex;
  429.     p->number = number;
  430.     p->subjectOffset = subjectOffset;
  431.     p->authorOffset = authorOffset;
  432.     GetDateTime(&p->creationDateTime);
  433.     
  434.     /* Increment the counter in the group info array. */
  435.     
  436.     (*gGroupInfo)[groupIndex].numCached++;
  437.     
  438.     gCacheDirty = true;
  439.     
  440.     return noErr;
  441. }
  442.  
  443.  
  444.  
  445. /*----------------------------------------------------------------------------
  446.     DeleteCachedArticle 
  447.     
  448.     Remove an article from the cache.
  449.     
  450.     Entry:    groupName = group name.
  451.             number = article number.
  452.             
  453.     Exit:    function result = error code.
  454. ----------------------------------------------------------------------------*/
  455.  
  456. OSErr DeleteCachedArticle (char *groupName, long number)
  457. {
  458.     long groupIndex, i;
  459.     TGroupInfo *q;
  460.     TArticleInfo *p;
  461.  
  462.     if (gGroupInfo == nil) return noErr;
  463.  
  464.     for (groupIndex = 0, q = *gGroupInfo; groupIndex < gNumGroupInfo; groupIndex++, q++) {
  465.         if (strcmp(groupName, *gStrings + q->offset) == 0) break;
  466.     }
  467.     if (groupIndex >= gNumGroupInfo) return noErr;
  468.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  469.         if (p->groupIndex == groupIndex && p->number == number) {
  470.             p->groupIndex = -1;
  471.             (*gGroupInfo)[groupIndex].numCached--;
  472.             gCacheDirty = true;
  473.             return noErr;
  474.         }
  475.     }
  476.     return noErr;
  477. }
  478.  
  479.  
  480.  
  481. /*----------------------------------------------------------------------------
  482.     CompareArticleNumbers 
  483.     
  484.     Compare an article number to the article number in a TSubject record.
  485.     
  486.     Entry:    *number = article number.
  487.             x = pointer to TSubject record
  488.             
  489.     Exit:    function result = 
  490.                 -1 if article number < article number in TSubject record.
  491.                 0 if article number = article number in TSubject record.
  492.                 +1 if article number > article number in TSubject record.
  493. ----------------------------------------------------------------------------*/
  494.  
  495. static int CompareArticleNumbers (long *number, TSubject *x)
  496. {
  497.     if (*number < x->number) {
  498.         return -1;
  499.     } else if (*number == x->number) {
  500.         return 0;
  501.     } else {
  502.         return +1;
  503.     }
  504. }
  505.  
  506.  
  507.  
  508. /*----------------------------------------------------------------------------
  509.     AppendOneCachedArticle 
  510.     
  511.     Append one cached article for a group to the end of a subject array.
  512.     
  513.     Entry:    subjectArray = handle to subject array.
  514.             oldNumSubjects = number of elements in original subject array.
  515.             *numSubjects = current number of elements in subject array.
  516.             strings = handle to strings block for subject window.
  517.             number = article number.
  518.             subjectOffset = offset of subject string in strings block.
  519.             authorOffset = offset of author string in strings block.
  520.             
  521.     Exit:    function result = error code.
  522. ----------------------------------------------------------------------------*/
  523.  
  524. static OSErr AppendOneCachedArticle (TSubject **subjectArray, short oldNumSubjects,
  525.     short *numSubjects, Handle strings, long number, long subjectOffset, 
  526.     long authorOffset)
  527. {
  528.     TSubject *x;
  529.     OSErr err = noErr;
  530.     long sOffset, aOffset;
  531.     short sLen, aLen;
  532.     char state;
  533.     
  534.     /* Check to see if this article was already read from the net. */
  535.  
  536.     state = MyHGetState(subjectArray);
  537.     MyHLock(subjectArray);
  538.     x = bsearch(&number, *subjectArray, oldNumSubjects, sizeof(TSubject), 
  539.         (int(*)(const void *, const void *))CompareArticleNumbers);
  540.     MyHSetState(subjectArray, state);
  541.     if (x != nil) return noErr;
  542.     
  543.     /* Check for too many subjects. */
  544.     
  545.     if (*numSubjects >= 16000) {
  546.         ErrorMessageNumber(kStrTooManySubjects);
  547.         return userCanceledErr;
  548.     }
  549.     
  550.     /* Add the subject and author strings to the strings block for the 
  551.        subject window. */
  552.        
  553.     sLen = strlen(*gStrings + subjectOffset);
  554.     aLen = strlen(*gStrings + authorOffset);
  555.     sOffset = GetHandleSize(strings);
  556.     aOffset = sOffset + sLen + 1;
  557.     err = MySetHandleSize(strings, aOffset + aLen + 1);
  558.     if (err != noErr) return err;
  559.     strcpy(*strings + sOffset, *gStrings + subjectOffset);
  560.     strcpy(*strings + aOffset, *gStrings + authorOffset);
  561.        
  562.     /* Add a new TSubject element to the end of the subject array. */
  563.     
  564.     err = MySetHandleSize(subjectArray, (*numSubjects+1)*sizeof(TSubject));
  565.     if (err != noErr) return err;
  566.     x = &(*subjectArray)[*numSubjects];
  567.     x->number = number;
  568.     x->subjectOffset = sOffset;
  569.     x->authorOffset = aOffset;
  570.     x->read = true;
  571.     x->collapsed = gPrefs.showThreadsCollapsed;
  572.     x->inList = true;
  573.     x->drawTriangleFilled = false;
  574.     x->onlyRedrawTriangle = false;
  575.     x->onlyRedrawCheck = false;
  576.     (*numSubjects)++;
  577.     return noErr;
  578. }
  579.  
  580.  
  581.  
  582. /*----------------------------------------------------------------------------
  583.     AppendCachedArticles 
  584.     
  585.     Append all the cached articles for a group to the end of the subject
  586.     array for a subject window.
  587.     
  588.     Entry:    wind = pointer to subject window.
  589.             
  590.     Exit:    function result = error code.
  591. ----------------------------------------------------------------------------*/
  592.  
  593. OSErr AppendCachedArticles (WindowPtr wind)
  594. {
  595.     TWindow **info;
  596.     CStr255 groupName;
  597.     TSubject **subjectArray;
  598.     short numSubjects, oldNumSubjects;
  599.     long groupIndex, i;
  600.     TGroupInfo *q;
  601.     TArticleInfo *p;
  602.     Handle strings;
  603.     OSErr err = noErr;
  604.     
  605.     if (gGroupInfo == nil) return noErr;
  606.     
  607.     info = (TWindow**)GetWRefCon(wind);
  608.     strcpy(groupName, *gGroupNames + (**info).groupNameOffset);
  609.     subjectArray = (**info).subjectArray;
  610.     numSubjects = oldNumSubjects = (**info).numSubjects;
  611.     strings = (**info).strings;
  612.     
  613.     for (groupIndex = 0, q = *gGroupInfo; groupIndex < gNumGroupInfo; groupIndex++, q++) {
  614.         if (strcmp(groupName, *gStrings + q->offset) == 0) break;
  615.     }
  616.     if (groupIndex >= gNumGroupInfo) return noErr;
  617.     
  618.     for (i = 0; i < gNumArticleInfo; i++) {
  619.         p = &(*gArticleInfo)[i];
  620.         if (p->groupIndex == groupIndex) {
  621.             err = AppendOneCachedArticle(subjectArray,  oldNumSubjects, 
  622.                 &numSubjects, strings, p->number, p->subjectOffset, 
  623.                 p->authorOffset);
  624.             if (err != noErr) return err;
  625.         }
  626.     }
  627.     
  628.     (**info).numSubjects = numSubjects;
  629.     
  630.     return noErr;
  631. }
  632.  
  633.  
  634.  
  635. /*----------------------------------------------------------------------------
  636.     AgeCache 
  637.     
  638.     Age the cached articles for a group.
  639.     
  640.     Entry:    groupName = group name.
  641.             low = low article number for this group on the server.
  642.             
  643.     Exit:    function result = error code.
  644.                 
  645.     All cached articles for the group with article numbers less than
  646.     the low article number are deleted.
  647. ----------------------------------------------------------------------------*/
  648.  
  649. OSErr AgeCache (char *groupName, long low)
  650. {
  651.     long groupIndex, i;
  652.     TGroupInfo *q;
  653.     TArticleInfo *p;
  654.     
  655.     if (gGroupInfo == nil) return noErr;
  656.  
  657.     for (groupIndex = 0, q = *gGroupInfo; groupIndex < gNumGroupInfo; groupIndex++, q++) {
  658.         if (strcmp(groupName, *gStrings + q->offset) == 0) break;
  659.     }
  660.     if (groupIndex >= gNumGroupInfo) return noErr;
  661.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  662.         if (p->groupIndex == groupIndex && p->number < low) {
  663.             p->groupIndex = -1;
  664.             (*gGroupInfo)[groupIndex].numCached--;
  665.             gCacheDirty = true;
  666.         }
  667.     }
  668.     return noErr;
  669. }
  670.  
  671.  
  672.  
  673. /*----------------------------------------------------------------------------
  674.     DumpCacheToFile 
  675.     
  676.     Dump the cache to a text file in human readable format (development
  677.     version only).
  678.     
  679.     Entry:    fSpec = pointer to file spec.
  680.             
  681.     Exit:    function result = error code.
  682. ----------------------------------------------------------------------------*/
  683.  
  684. #ifdef kDevelopmentVersion
  685.  
  686. OSErr DumpCacheToFile (FSSpec *fSpec)
  687. {
  688.     OSErr err = noErr;
  689.     short refNum = 0;
  690.     CStr255 msg;
  691.     Str255 dateString, timeString;
  692.     long len, numCache = 0;
  693.     TArticleInfo *p;
  694.     TGroupInfo *q;
  695.     long i;
  696.     char state1, state2, state3;
  697.     
  698.     state1 = MyHGetState(gArticleInfo);
  699.     state2 = MyHGetState(gArticleInfo);
  700.     state3 = MyHGetState(gStrings);
  701.  
  702.     if (gGroupInfo == nil) {
  703.         ErrorMessage("There is no cache.");
  704.         return userCanceledErr;
  705.     }
  706.  
  707.     err = FSpOpenDF(fSpec, fsRdWrPerm, &refNum);
  708.     if (err != noErr) goto exit;
  709.  
  710.     MyHLock(gArticleInfo);
  711.     MyHLock(gGroupInfo);
  712.     MyHLock(gStrings);
  713.     
  714.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  715.         if (p->groupIndex >= 0) numCache++;
  716.     }
  717.     
  718.     for (i = 0, q = *gGroupInfo; i < gNumGroupInfo; i++, q++) {
  719.         sprintf(msg, "%9ld cached articles from %s\r", q->numCached, *gStrings + q->offset);
  720.         len = strlen(msg);
  721.         MyFSWriteNoCache(refNum, &len, msg, nil);
  722.     }
  723.     
  724.     sprintf(msg, "\r%9ld total articles in cache\r\r%9ld total bytes in cache\r\r",
  725.         numCache, MyGetHandleSize(gGroupInfo) + MyGetHandleSize(gArticleInfo) +
  726.         MyGetHandleSize(gStrings));
  727.     len = strlen(msg);
  728.     MyFSWriteNoCache(refNum, &len, msg, nil);
  729.     
  730.     for (i = 0, p = *gArticleInfo; i < gNumArticleInfo; i++, p++) {
  731.         if (p->groupIndex >= 0) {
  732.             sprintf(msg, "%ld\r   %s:%ld\r", i, 
  733.                 *gStrings + (*gGroupInfo)[p->groupIndex].offset,
  734.                 p->number);
  735.              len = strlen(msg);
  736.             MyFSWriteNoCache(refNum, &len, msg, nil);
  737.             sprintf(msg, "   %s\r", *gStrings + p->subjectOffset);
  738.             len = strlen(msg);
  739.             MyFSWriteNoCache(refNum, &len, msg, nil);
  740.             sprintf(msg, "   %s\r", *gStrings + p->authorOffset);
  741.             len = strlen(msg);
  742.             MyFSWriteNoCache(refNum, &len, msg, nil);
  743.             IUDateString(p->creationDateTime, shortDate, dateString);
  744.             IUTimeString(p->creationDateTime, false, timeString);
  745.             p2cstr(dateString);
  746.             p2cstr(timeString);
  747.             sprintf(msg, "   Created %s %s\r", dateString, timeString);
  748.             len = strlen(msg);
  749.             MyFSWriteNoCache(refNum, &len, msg, nil);
  750.         } else {
  751.             sprintf(msg, "%ld\r   *** unused ***\r", i);
  752.             len = strlen(msg);
  753.             MyFSWriteNoCache(refNum, &len, msg, nil);
  754.         }
  755.     }
  756.     
  757.     MyHSetState(gArticleInfo, state1);
  758.     MyHSetState(gGroupInfo, state2);
  759.     MyHSetState(gStrings, state3);
  760.     
  761.     MyFSClose(refNum, nil);
  762.     return noErr;
  763.     
  764. exit:
  765.     
  766.     if (refNum != 0) MyFSClose(refNum, nil);
  767.     MyHSetState(gArticleInfo, state1);
  768.     MyHSetState(gGroupInfo, state2);
  769.     MyHSetState(gStrings, state3);
  770.     return err;
  771. }
  772.  
  773. #endif 
  774.  
  775.  
  776.  
  777. /*----------------------------------------------------------------------------
  778.     DisplayCache 
  779.     
  780.     Display the cache in a text window in human readable format (development
  781.     version only).
  782.     
  783.     Exit:    function result = error code.
  784. ----------------------------------------------------------------------------*/
  785.  
  786. #ifdef kDevelopmentVersion
  787.  
  788. OSErr DisplayCache (void)
  789. {
  790.     FSSpec fSpec;
  791.     OSErr err = noErr;
  792.     Handle text = nil;
  793.     short refNum = 0;
  794.     long len;
  795.     WindowPtr wind;
  796.  
  797.     err = CreateTemporaryFile(&fSpec, kNewsWatcherSignature, 'ttxt', 'TEXT');
  798.     if (err != noErr) goto exit;
  799.     err = DumpCacheToFile(&fSpec);
  800.     if (err != noErr) goto exit;
  801.     err = FSpOpenDF(&fSpec, fsRdPerm, &refNum);
  802.     if (err != noErr) goto exit;
  803.     err = MyNewHandle(0x7fff, &text);
  804.     if (err != noErr) goto exit;
  805.     len = 0x7fff;
  806.     MyHLock(text);
  807.     err = FSRead(refNum, &len, *text);
  808.     MyHUnlock(text);
  809.     MySetHandleSize(text, len);
  810.     MyFSClose(refNum, nil);
  811.     err = MakeNewTextWindow("\pArticle Cache", 0, nil, text, &wind);
  812.     if (err != noErr) goto exit;
  813.     MyDisposeHandle(text);
  814.     return noErr;
  815.     
  816. exit:
  817.  
  818.     if (refNum != 0) MyFSClose(refNum, nil);
  819.     MyDisposeHandle(text);
  820.     return err;
  821. }
  822.  
  823. #endif